<?php

namespace IYUU\sdk\CoolQ;

use BadRequestException;
use EventDispatcher;
use Logger;
use Yaf\Registry;

/**
 * OneBot机器人 事件处理基类
 * 接口文档：https://docs.go-cqhttp.org/event
 * Class Event
 * @package IYUU\sdk\CoolQ
 */
abstract class Event
{
    /**
     * 事件发生的时间戳
     * @var int
     */
    protected $time = 0;

    /**
     * 收到事件的机器人 QQ 号
     * @var int
     */
    protected $self_id = 0;

    /**
     * 上报的事件类型（消息message，通知notice，请求request）
     * @var string
     */
    protected $post_type = '';

    /**
     * 消息子类型
     * @var string
     */
    protected $sub_type = '';

    /**
     * 群号
     * @var int
     */
    protected $group_id = 0;

    /**
     * 临时会话来源
     * - 0 群聊、1 QQ咨询、2 查找、3 QQ电影、4 热聊、6 验证消息、7 多人聊天、8 约会、9 通讯录
     * @var int
     */
    protected $temp_source = null;

    /**
     * 发送者 QQ 号
     * @var int|string
     */
    protected $user_id = 0;

    /**
     * 匿名信息, 如果不是匿名消息则为 null
     * @var string
     */
    protected $anonymous = null;

    /**
     * 消息内容
     * @var string
     */
    protected $message = '';

    /**
     * 原始消息内容
     * @var string
     */
    protected $raw_message = '';

    /**
     * 字体
     * @var int
     */
    protected $font = null;

    /**
     * 发送人信息
     *
     * - 私聊消息内容如：发送者qq号{user_id}、昵称{nickname}、性别{sex}(性别, male 或 female 或 unknown)、年龄{age}
     * - 群消息 内容如：发送者qq号{user_id}、昵称{nickname}、性别{sex}(male 或 female 或 unknown)、年龄{age}、群名片／备注{card}、地区{area}、成员等级{level}、角色{role}(owner 或 admin 或 member)、专属头衔{title}
     * - 需要注意的是, sender中的各字段是尽最大努力提供的, 不保证每个字段都一定存在, 也不保证存在的字段都是完全正确的 (缓存可能过期)
     * - 尤其对于匿名群消息, 此字段不具有参考价值。
     * @var array
     */
    protected $sender = [];

    /**
     * 构造函数
     * 主要做的事情：1.DecodeCQCode；2.初始化成员；3.注册上下文对象
     * Context constructor.
     * @param Request $request
     */
    final public function __construct(Request $request)
    {
        // 获得全部请求参数【OneBot机器人上报事件的完整数据】
        $Yaf_CoolQ_Event = $request->getAll();
        if (!empty($Yaf_CoolQ_Event)) {
            // 1. 解码消息
            if (isset($Yaf_CoolQ_Event['message'])) {
                $Yaf_CoolQ_Event['message'] = trim(CoolQCode::DecodeCQCode($Yaf_CoolQ_Event['message']));
            }

            // 2. 初始化成员
            foreach ($Yaf_CoolQ_Event as $key => $value) {
                $this->$key = $value;
            }

            // 3. 上下文对象
            Registry::set(Registry_CONTEXT_NAME, new Context($Yaf_CoolQ_Event));

            // 4. 事件调度器
            if (true) {
                // 注册一个会在php中止时执行的函数，派发本次事件到所有订阅的模块
                register_shutdown_function(array($this, 'EventDispatcher'));
            }
        } else {
            // 上下文对象(空对象)
            Registry::set(Registry_CONTEXT_NAME, new Context());
        }
    }

    /**
     * 事件调度器
     * - 派发本次事件到所有订阅的模块
     */
    final public function EventDispatcher()
    {
        $moduleListener = $this->getConfig('receiver.moduleListener');
        if (!empty($moduleListener)) {
            $dispatcher = new EventDispatcher($moduleListener);
            $dispatcher->dispatch($this);
        }
    }

    /**
     * 默认回调器：根据传入的方法名，执行当前类的对应方法
     * - 返回值必须为数组
     * - 举例：收到群聊消息$action_name的值为group，拼接后要调用的方法就是$this->callable_group()
     *
     * @param string $action_name
     * @return array
     */
    final protected function callableAction(string $action_name): array
    {
        if (empty($action_name)) {
            return [];
        }
        // 1. 拼接将要调用的方法名称
        $callableActionName = 'callable_' . strtolower($action_name);
        // 2. 检查方法是否可调用
        if (method_exists($this, $callableActionName) && is_callable(array($this, $callableActionName))) {
            // 3. 调用方法
            $resp = $this->$callableActionName();
            if (!empty($resp) && is_array($resp)) {
                return $resp;
            }
        }
        return [];
    }

    /**
     * 获得当前机器人的配置信息（支持 . 分隔符）
     * @param string|int $key
     * @param null $default
     * @return mixed|null
     */
    final public function getConfig($key = null, $default = null)
    {
        return getConfig($key, $default);
    }

    /**
     * 获取上下文
     * @return Context
     */
    final public function getContext(): Context
    {
        return getContext();
    }

    /**
     * QQ主人才能进行的操作，权限不足报异常
     * @return bool
     * @throws BadRequestException
     */
    final public function requireMaster(): bool
    {
        return $this->isMaster(true);
    }

    /**
     * QQ工作人员才能进行的操作，权限不足报异常
     * @return bool
     * @throws BadRequestException
     */
    final public function requireWorker(): bool
    {
        return $this->isWorker(true);
    }

    /**
     * 判断当前QQ是否为工作人员
     * @param bool $isStopIfNoUnauthorized
     * @return bool
     * @throws BadRequestException
     */
    final public function isWorker(bool $isStopIfNoUnauthorized = false): bool
    {
        if (in_array((int)$this->user_id, getConfig('worker')) || $this->isMaster()) {
            return true;
        }

        // 权限不足时，是否停止
        if ($isStopIfNoUnauthorized) {
            throw new BadRequestException('权限不足[CQ:face,id=41]', 1);
        }
        return false;
    }

    /**
     * 判断当前QQ是否为主人
     * @param bool $isStopIfNoUnauthorized
     * @return bool
     * @throws BadRequestException
     */
    final public function isMaster(bool $isStopIfNoUnauthorized = false): bool
    {
        if (((int)$this->user_id) === getConfig('master')) {
            return true;
        }

        // 权限不足时，是否停止
        if ($isStopIfNoUnauthorized) {
            throw new BadRequestException('权限不足[CQ:face,id=41]', 1);
        }
        return false;
    }

    /**
     * 判断当前QQ是否为机器人
     * @return bool
     */
    final public function isBot(): bool
    {
        return ((int)$this->user_id) === getConfig('bot');
    }

    /**
     * 判断是否AT机器人
     * @return bool     true表示AT
     */
    final public function isAtBot(): bool
    {
        $at = CoolQCode::At($this->self_id);
        return stripos($this->message, $at) !== false;
    }

    /**
     * 移除AT机器人的CQ码
     * @return string
     */
    final public function removeAtBot(): string
    {
        $at = CoolQCode::At($this->self_id);
        return trim(str_replace([$at, ' '], '', $this->message));
    }

    /**
     * 给事件产生者发送私聊
     * @param string $msg 消息内容
     * @param bool $auto_escape 是否发送纯文本
     * @param int $expire 消息有效期（过期撤回）
     */
    final public function sendPM(string $msg, bool $auto_escape = false, int $expire = 0)
    {
        // 检测私聊消息发送开关 2021年8月27日
        if (empty($this->getConfig('enabled.private'))) {
            Logger::log('OneBot', __METHOD__ . '因消息发送开关关闭，本次消息未发送：' . PHP_EOL . $msg);
            return;
        }

        $this->enqueue(new Message($msg, $this->user_id, false, $auto_escape, $expire));
    }

    /**
     * 消息从哪来发到哪
     * @param string $msg 消息内容
     * @param bool $auto_escape 是否发送纯文本
     * @param bool $at_sender 是否要在回复开头 at 发送者, 发送者是匿名用户时无效(默认at发送者)
     * @param int $expire 消息有效期（过期撤回）
     */
    final public function sendBack(string $msg, bool $auto_escape = false, bool $at_sender = true, int $expire = 0)
    {
        // 检测消息发送开关 2021年8月27日
        $message_type = !empty($this->group_id) ? 'group' : 'private';
        if (empty($this->getConfig('enabled.' . $message_type))) {
            Logger::log('OneBot', __METHOD__ . '因消息发送开关关闭，本次消息未发送：' . PHP_EOL . $msg);
            return;
        }

        $at = '';
        if (!empty($this->group_id) && $at_sender) {
            $at = CoolQCode::At($this->user_id);
        }
        $id = !empty($this->group_id) ? $this->group_id : $this->user_id;
        $this->enqueue(new Message($at . $msg, $id, !empty($this->group_id), $auto_escape, $expire));
    }

    /**
     * 发送给 Master
     * @param string $msg 消息内容
     * @param bool $auto_escape 是否发送纯文本
     */
    final public function sendMaster(string $msg, bool $auto_escape = false)
    {
        // 检测私聊消息发送开关 2021年8月27日
        if (empty($this->getConfig('enabled.private'))) {
            Logger::log('OneBot', __METHOD__ . '因消息发送开关关闭，本次消息未发送：' . PHP_EOL . $msg);
            return;
        }

        $this->enqueue(new Message($msg, $this->getConfig('master'), false, $auto_escape));
    }

    /**
     * 往队列尾部添加成员
     * @note dequeue方法等效于父类push方法
     * @param Message $message
     * @see splDoublyLinkedList::push()
     */
    final public function enqueue(Message $message)
    {
        getOneBot()->enqueue($message);
    }

    /**
     * 初始化方法
     * - 可由开发人员根据需要重载
     * - 此方法会在构造方法(__construct)运行之后被调用 （参见EventFactory类）
     * @throws BadRequestException
     */
    public function init()
    {
        $this->filterCheck();
        $this->userCheck();
    }

    /**
     * 调用过滤器
     * - 可由开发人员根据需要重载
     * @return void
     * @throws BadRequestException
     */
    protected function filterCheck()
    {
        // 过滤器检查
        // 1. 检查IP白名单(拒绝未授权的上报)
        $ip_whitelist = $this->getConfig('ip_whitelist', []);
        $client_ip = $_SERVER['REMOTE_ADDR'];
        if (!empty($ip_whitelist) && !in_array($client_ip, $ip_whitelist)) {
            throw new BadRequestException($client_ip . '不在IP白名单', 1);
        }
    }

    /**
     * 调用用户检查
     * - 可由开发人员根据需要重载
     * @return void
     */
    protected function userCheck()
    {
        // 用户检查
    }

    /**
     * 抽象方法，子类必须实现
     * @param Context $context
     * @return mixed
     */
    abstract public function index(Context $context): array;
}
